/*
* INSANE - Interactive Structural Analysis Environment
*
* Copyright (C) 2003-2004
* Universidade Federal de Minas Gerais
* Escola de Engenharia
* Departamento de Engenharia de Estruturas
*
* Author's email : insane@dees.ufmg.br
* Author's Website : http://www.dees.ufmg.br/insane
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package br.ufmg.dees.insane.model.xfemModel.xfemElement;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import br.ufmg.dees.insane.materialMedia.degeneration.Degeneration;
import br.ufmg.dees.insane.model.femModel.element.ParametricElement;
import br.ufmg.dees.insane.model.femModel.elementNode.ElementNode;
import br.ufmg.dees.insane.model.xfemModel.enrichmentItem.CrackInterior;
import br.ufmg.dees.insane.model.xfemModel.enrichmentItem.EnrichmentItem;
import br.ufmg.dees.insane.model.xfemModel.xfemelementNode.XFEMElementNode;
import br.ufmg.dees.insane.util.IPoint3d;
import br.ufmg.dees.insane.util.IVector;
import br.ufmg.dees.insane.util.xfem.Line;
import br.ufmg.dees.insane.util.xfem.Segment;

/**
 * @author Kelson Wolff
 *
 */
public abstract class XFEMElement extends ParametricElement {

	/**
	 * A marker to know this XFEMElement is needed to recompute its
	 * stiffness matrix when the EnrichmentItem involves as time goes by.
	 */
	protected boolean isUpdated;
	
	/**
	 * The List that stores every EnrichmentItem object interacting
	 * with this XFEMElement.
	 */
	protected List<EnrichmentItem> enrichmentItemList;
	
	/**
	 * Auxiliar temporary field representing the stress vector associated at each gauss point 
	 * of this element (for NonLocalPrincipalStressDirectionTest).
	 */
	protected Map<Degeneration,IVector> stressMap;
	
	/* (non-Javadoc)
	 * @see br.ufmg.dees.insane.model.femModel.element.ParametricElement#addDegeneration(br.ufmg.dees.insane.materialMedia.degeneration.Degeneration)
	 */
	@Override
	public abstract void addDegeneration(Degeneration dg);
	
	/**
	 * Adds the provided Degeneration list to Degeneration
	 * list that belongs to this XFEMElement.
	 * @param dgs
	 */
	public void addDegenerations(List<Degeneration> dgs){
		
	}

	/* (non-Javadoc)
	 * @see br.ufmg.dees.insane.model.femModel.element.ParametricElement#getElmCoefficient()
	 */
	@Override
	public abstract double getElmCoefficient(); 

	/* (non-Javadoc)
	 * @see br.ufmg.dees.insane.model.femModel.element.ParametricElement#initDegenerations()
	 */
	@Override
	public abstract void initDegenerations();
	
	/**
	 * @return Returns the number of nodes that belongs to this element.
	 */
	public int getNbNodes(){
		return (this.getIncidence()).size();
	}
	
	/**
	 * Sets the incidence of this Element and sets this Element at each node inside incidence.
	 * @param a  The incidence of this Element.
	 * @uml.property  name="incidence"
	 */
//	public void setIncidence(ArrayList<ElementNode> a) {
//		incidence = a;
//		ListIterator incid = this.getIncidence().listIterator();
//		while(incid.hasNext()){
//			ElementNode node = (ElementNode) incid.next();
//			((XFEMElementNode)node).setElementsThatIbelongTo(this);
//		}
//	}
	
	/** 
	 * Sets the node to this Element and sets this Element at the node.
	 * @param a A Node a in a specified position b.
	 * @param b The position of the Node in the incidence.
	 */
//	public void setNode(XFEMElementNode a, int b){
//		if (incidence.size() != b) {
//			incidence.remove(b);
//			incidence.add(b, a);
//			a.setElementsThatIbelongTo(this);
//		}
//		else{
//			incidence.add(a);
//			a.setElementsThatIbelongTo(this);
//		}
//	}
	
	/**
	 * Adds the provided elementNode a to this
	 * Element's incidence.
	 * @param a The provided XFEMElementNode.
	 */
	public void addXFEMElmNode(XFEMElementNode a) {
		a.setElementsThatIbelongTo(this);
		incidence.add((XFEMElementNode)a);
	}

	/**
	 * Gets all neighbors elements around this Element, excluding himself.
	 * @return Returns a List that contains all the elements around this Element, excluding himself.
	 */
	public List<XFEMElement> getMyNeighbors(){
		List<XFEMElement> myNeighbors = new ArrayList<XFEMElement>();
		ListIterator myIncidence = this.getIncidence().listIterator();
		while(myIncidence.hasNext()){
			ElementNode actualNode = (ElementNode) myIncidence.next();
			myNeighbors.addAll(((XFEMElementNode)actualNode).getElementsThatIbelongTo());
		}
		// eliminates the repeated objects inside myNeighbors List
		Collection<XFEMElement> neighbors = new HashSet<XFEMElement>(myNeighbors);
		// eliminates the receiver element
		neighbors.remove(this);
		return (new ArrayList<XFEMElement>(neighbors));
	}
	
	/**
	 * Gets the area of this Element.
	 * @return Returns the area of this Element.
	 */
	public abstract double getMyArea();
	
	/**
	 * Gets the number of faces of this Element.
	 * @return Returns the number of faces of this Element.
	 */
	public abstract int getNbFaces();
	
	/**
	 * Gets the number of nodes pertaining at each face of this Element.
	 * @return Returns the number of nodes pertaining at each face of this Element.
	 */
	public abstract int getNbNodesFace();
	
	/**
	 * Gets the Incidence List (Nodes List) of this Element setted in a counter-clock-wise order.
	 * @return Returns the incidence List of this Element setted in a counter-clock-wise order.
	 */
	public abstract ArrayList getCCWIncidence();
	
	/**
	 * Gets the Incidence Array (Nodes Array)of this Element that hold the label of each node
	 * getted in a counter-clock-wise order.
	 * @return Returns the Incidence Array of this Element that hold the label of each node
	 * getted in a counter-clock-wise order.
	 */
	public abstract String[] getCCWIncidenceString();
	
	/**
	 * Sets the Incidence List (Nodes List) of this Element in a 
	 * counter-clock-wise order.
	 */
	public abstract void setCCWIncidence();
	
	/**
	 * Gets the List of the nodal coordinates of this Element. This List includes 
	 * all nodes coordinates of this Element.
	 * @return Returns the List of the nodal coordinates of this Element.
	 */
	public ArrayList<double[]> getNodalCoords(){
		ArrayList<double[]> coordsArray = new ArrayList<double[]>();
		ArrayList<ElementNode> incid = new ArrayList<ElementNode>();
		incid.addAll(this.getIncidence());
		ListIterator nodes = incid.listIterator();
		while(nodes.hasNext()){
			XFEMElementNode node = (XFEMElementNode) nodes.next();
			double[] coords = new double[3];
			coords[0] = node.getX();
			coords[1] = node.getY();
			coords[2] = node.getZ();
			coordsArray.add(coords);
		}
		return coordsArray;
	}
	
	/**
	 * Gets the Array of the nodal coordinates of this Element. This List includes 
	 * all nodes coordinates of this Element.
	 * @return Returns the Array of the nodal coordinates of this Element.
	 */
	public double[][] getNodalCoordsDouble(){
		double[][] coordsArray = new double[this.getNbNodes()][3];
		ArrayList<ElementNode> incid = new ArrayList<ElementNode>();
		incid.addAll(this.getIncidence());
		ListIterator nodes = incid.listIterator();
		int i = 0;
		while(nodes.hasNext()){
			XFEMElementNode node = (XFEMElementNode) nodes.next();
			coordsArray[i][0] = node.getX();
			coordsArray[i][1] = node.getY();
			coordsArray[i][2] = node.getZ();
			i++;
		}
		return coordsArray;
	}
	
	/**
	 * Gets the List of incidence labels pertaining to each face (edge) of this Element.
	 * @return Returns the List of incidence labels pertaining to each face (edge) of this Element.
	 */
	public ArrayList<String[]> getFacesIncidenceList(){
		ArrayList<String[]> facesIncidence = new ArrayList<String[]>();
		ArrayList<String> labels = new ArrayList<String>();
		ArrayList<ElementNode> incid = new ArrayList<ElementNode>();
		incid.addAll(this.getIncidence());
		incid.add((this.getIncidence()).get(0));
		incid.trimToSize();
		ListIterator nodes = incid.listIterator();
		while(nodes.hasNext()){
			XFEMElementNode node = (XFEMElementNode) nodes.next();
			labels.add(node.getLabel());
		}
		String[] faceLabels = new String[this.getNbNodesFace()];
		ListIterator lbls = labels.listIterator();
		for(int i=0; i<this.getNbFaces(); i++){
			for(int j=0; j<this.getNbNodesFace(); j++){
				faceLabels[j] = (String) lbls.next();
			}
			facesIncidence.add(faceLabels);
			lbls.previous();
		}
		return facesIncidence;
	}
	
	/**
	 * Gets the Array of incidence labels pertaining to each face (edge) of this Element.
	 * @return Returns the Array of incidence labels pertaining to each face (edge) of this Element.
	 */
	public String[][] getFacesIncidenceArray(){
		
		String[][] facesIncidence = new String[this.getNbFaces()][this.getNbNodesFace()];
		ArrayList<String> labels = new ArrayList<String>();
		ArrayList<ElementNode> incid = new ArrayList<ElementNode>();
		incid.addAll(this.getIncidence());
		incid.add((this.getIncidence()).get(0));
		ListIterator nodes = incid.listIterator();
		while(nodes.hasNext()){
			XFEMElementNode node = (XFEMElementNode) nodes.next();
			labels.add(node.getLabel());
		}
		ListIterator lbls = labels.listIterator();
		for(int i=0; i<this.getNbFaces(); i++){
			for(int j=0; j<this.getNbNodesFace(); j++){
				facesIncidence[i][j] = (String) lbls.next();
			}
			lbls.previous();
		}
		return facesIncidence;
	}
	
	/**
	 * Auxiliar temporary method for NonLocalPrincipalStressDirectionTest.
	 * @param sigmas The list of stress vectors of this Element associated at each degeneration.
	 */
	public void setStress(ArrayList<IVector> sigmas){
		ArrayList<Degeneration> dgs = this.getDegenerations();
		stressMap = new HashMap<Degeneration,IVector>(dgs.size());
		int index = 0;
		ListIterator degens = dgs.listIterator();
		while(degens.hasNext()){
			Degeneration degen = (Degeneration) degens.next();
			stressMap.put(degen,sigmas.get(index));
			index++;
		}
	}
	
	/**
	 * Auxiliar temporary method for NonLocalPrincipalStressDirectionTest.
	 * @param dg The degeneration (gauss point) pertaining to this element.
	 * @return The stress vector associated at degeneration dg.
	 */
	public IVector getStress(Degeneration dg){
		return (IVector)stressMap.get(dg);
	}
	
	/**
	 * @return Returns the enrichmentItemList.
	 */
	public List<EnrichmentItem> getEnrichmentItemList() {
		return enrichmentItemList;
	}
	
	/**
	 * @param i The position of the enrichment item.
	 * @return Returns the enrichment item at position i.
	 */
	public EnrichmentItem getEnrichmentItem(int i) {
		return enrichmentItemList.get(i);
	}

	/**
	 * @param enrichmentItemList The enrichmentItemList to set.
	 */
	public void setEnrichmentItemList(List<EnrichmentItem> enrichmentItemList) {
		this.enrichmentItemList = enrichmentItemList;
	}
	
	/**
	 * @param enrichItem The enrichment item to set.
	 */
	public void setEnrichmentItem(EnrichmentItem enrichItem) {
		this.enrichmentItemList.add(enrichItem);
	}

	/**
	 * @return Returns the isUpdated.
	 */
	public boolean isUpdated() {
		return isUpdated;
	}

	/**
	 * @param isUpdated The isUpdated to set.
	 */
	public void setUpdated(boolean isUpdated) {
		this.isUpdated = isUpdated;
	}
	
	/**
	 * Check if this element interacts with each provided enrichment item or not. 
	 * If so, insert this element into the list of each enrichment item.
	 * @param enrItemList The List holding each enrichment item of the XFEM Model.
	 */
	public void treatGeoMeshInteraction(List<EnrichmentItem> enrItemList){
		ListIterator eItemIt = enrItemList.listIterator();
		while(eItemIt.hasNext()){
			EnrichmentItem enrItem = (EnrichmentItem) eItemIt.next();
			if(enrItem instanceof CrackInterior){
				CrackInterior crackInt = (CrackInterior) enrItem;
				crackInt.treatMeshGeoInteraction(this);
			}			
		}
	}
	
	/**
	 * 
	 * @return
	 */
	public boolean isEnriched(){
		return false;
	}
	
	/**
	 * If this element is enriched with provided enrichment item enrItem,  
	 * then insert enrItem into the list of enrichment items.
	 * @param enrItem The enrichment item to set.
	 */
	public void isEnrichedWith(EnrichmentItem enrItem){
		if(this.enrichmentItemList == null){
			this.enrichmentItemList = new ArrayList<EnrichmentItem>();
		}
		if(!this.getEnrichmentItemList().contains(enrItem)){
			this.setEnrichmentItem(enrItem);
		}
	}

	/**
	 * 
	 * @param enrItem
	 */
	public abstract void setEnrichmentForMyNodes(EnrichmentItem enrItem);
	
	/**
	 * Returns true if provided point testPoint is on any edge of this
	 * XFEMElement or false if it isn't.
	 * @param testPoint The provided point for testing.
	 * @return True if provided point testPoint is on any edge of this
	 * XFEMElement or false if it isn't.
	 */
	public boolean isOnEdge(IPoint3d testPoint){
		List<IPoint3d> pts = new ArrayList<IPoint3d>();
		for(int i=0; i<this.getNbNodes(); i++){
			 XFEMElementNode node = (XFEMElementNode) this.getNode(i);
			 IPoint3d p = new IPoint3d(node.getX(),node.getY());
			 pts.add(p);
		}
		
		boolean found = false;
		Segment s = new Segment();
		int i = 0;
		while((i<pts.size()) && (!found)){
			if(i<(pts.size()-1)){
				s.setFirstPoint((IPoint3d)pts.get(i));
				s.setSecondPoint((IPoint3d)pts.get(i+1));
			}
			if(i == (pts.size()-1)){
				s.setFirstPoint((IPoint3d)pts.get(i));
				s.setSecondPoint((IPoint3d)pts.get(0));	
			}
			double x = s.getSecondPoint().getX()-s.getFirstPoint().getX();
			double y = s.getSecondPoint().getY()-s.getFirstPoint().getY();
			s.setVector(new IPoint3d(x,y));
			
			if(s.on(testPoint))
				found = true;
			i++;
		}
		return found;
	}
	
	/**
	 * Builds a new instance of this XFEMElement making a copy
	 * of each field to the new instance fields.
	 * @return A clone of this XFEMElement.
	 */
	public abstract XFEMElement cloneMyself();
	
	/**
	 * 
	 *
	 */
	public void reinitializeStateOfElement(){
		this.isUpdated = false;
	}
	
	/**
	 * 
	 *
	 */
	public void setStateOfElement(){
		this.isUpdated = true;
	}
	
	/**
	 * Clears the List of Enrichment Items
	 * that belong to this XFEMElement.
	 */
	public void eraseListOfEnrichmentItems(){
		this.enrichmentItemList.clear();
	}
	
	/**
	 * Returns true if there is intersection between this XFEMElement
	 * and provided Segment seg and false if there isn't.
	 * @param seg The provided Segment.
	 * @return True if there is intersection between this XFEMElement
	 * and provided Segment seg and false if there isn't.
	 */
	public boolean intersects(Segment seg){
		
		List<IPoint3d> pts = new ArrayList<IPoint3d>();
		ListIterator incid = this.getIncidence().listIterator();
		while(incid.hasNext()){
			XFEMElementNode node = (XFEMElementNode) incid.next();
			IPoint3d point = new IPoint3d(node.getX(),node.getY());
			pts.add(point);
		}
		
		boolean ret = false;
		Segment s = new Segment();
		for(int i=0; i<pts.size(); i++){
			if(i<(pts.size()-1)){
				s.setFirstPoint((IPoint3d)pts.get(i));
				s.setSecondPoint((IPoint3d)pts.get(i+1));
			}
			if(i == (pts.size()-1)){
				s.setFirstPoint((IPoint3d)pts.get(i));
				s.setSecondPoint((IPoint3d)pts.get(0));	
			}
			double x = s.getSecondPoint().getX()-s.getFirstPoint().getX();
			double y = s.getSecondPoint().getY()-s.getFirstPoint().getY();
			s.setVector(new IPoint3d(x,y));
			
			ret = ret || seg.intersects(s);
		}

		return ret;
	}
	
	/**
	 * Returns true if there is intersection between this XFEMElement
	 * and provided Line line and false if there isn't.
	 * @param line The provided Segment.
	 * @return True if there is intersection between this XFEMElement
	 * and provided Line line and false if there isn't.
	 */
	public boolean intersects(Line line){
		
		List<IPoint3d> pts = new ArrayList<IPoint3d>();
		ListIterator incid = this.getIncidence().listIterator();
		while(incid.hasNext()){
			XFEMElementNode node = (XFEMElementNode) incid.next();
			IPoint3d point = new IPoint3d(node.getX(),node.getY());
			pts.add(point);
		}
		
		boolean ret = false;
		Segment s = new Segment();
		for(int i=0; i<pts.size(); i++){
			if(i<(pts.size()-1)){
				s.setFirstPoint((IPoint3d)pts.get(i));
				s.setSecondPoint((IPoint3d)pts.get(i+1));
			}
			if(i == (pts.size()-1)){
				s.setFirstPoint((IPoint3d)pts.get(i));
				s.setSecondPoint((IPoint3d)pts.get(0));	
			}
			double x = s.getSecondPoint().getX()-s.getFirstPoint().getX();
			double y = s.getSecondPoint().getY()-s.getFirstPoint().getY();
			s.setVector(new IPoint3d(x,y));
			
			ret = ret || line.intersects(s);
		}

		return ret;
	}
	
	/**
	 * Returns true if this XFEMElement is located at mesh's 
	 * boundary (if one edge belonging to this element is
	 * located at mesh's boundary at least) and false if it isn't.
	 * @return True if this XFEMElement is located at mesh's 
	 * boundary and false if it isn't.
	 */
	public boolean isBoundaryElement(){
		int nbNeighSegs = 0;
		int nbMySegs = this.getNbNodes();
		
		List<IPoint3d> myPts = new ArrayList<IPoint3d>();
		ListIterator myIncid = this.getIncidence().listIterator();
		while(myIncid.hasNext()){
			XFEMElementNode myNode = (XFEMElementNode) myIncid.next();
			IPoint3d myPoint = new IPoint3d(myNode.getX(),myNode.getY());
			myPts.add(myPoint);
		}
		Segment mySeg = new Segment();
		for(int i=0; i<myPts.size(); i++){
			if(i<(myPts.size()-1)){
				mySeg.setFirstPoint((IPoint3d)myPts.get(i));
				mySeg.setSecondPoint((IPoint3d)myPts.get(i+1));
			}
			if(i == (myPts.size()-1)){
				mySeg.setFirstPoint((IPoint3d)myPts.get(i));
				mySeg.setSecondPoint((IPoint3d)myPts.get(0));	
			}
			double x = mySeg.getSecondPoint().getX()-mySeg.getFirstPoint().getX();
			double y = mySeg.getSecondPoint().getY()-mySeg.getFirstPoint().getY();
			mySeg.setVector(new IPoint3d(x,y));
			
			ListIterator neighbors = this.getMyNeighbors().listIterator();
			while(neighbors.hasNext()){
				XFEMElement neighElm = (XFEMElement) neighbors.next();
				List<IPoint3d> neighPts = new ArrayList<IPoint3d>();
				ListIterator neighIncid = neighElm.getIncidence().listIterator();
				while(neighIncid.hasNext()){
					XFEMElementNode neighNode = (XFEMElementNode) neighIncid.next();
					IPoint3d neighPoint = new IPoint3d(neighNode.getX(),neighNode.getY());
					neighPts.add(neighPoint);
				}
				Segment neighSeg = new Segment();
				for(int j=0; j<neighPts.size(); j++){
					if(j<(neighPts.size()-1)){
						neighSeg.setFirstPoint((IPoint3d)neighPts.get(j));
						neighSeg.setSecondPoint((IPoint3d)neighPts.get(j+1));
					}
					if(j == (neighPts.size()-1)){
						neighSeg.setFirstPoint((IPoint3d)neighPts.get(j));
						neighSeg.setSecondPoint((IPoint3d)neighPts.get(0));	
					}
					double nx = neighSeg.getSecondPoint().getX()-neighSeg.getFirstPoint().getX();
					double ny = neighSeg.getSecondPoint().getY()-neighSeg.getFirstPoint().getY();
					neighSeg.setVector(new IPoint3d(nx,ny));
					
					if(mySeg.isCoincidental(neighSeg))
						nbNeighSegs = nbNeighSegs + 1;
				}
			}
		}
		return ( (nbNeighSegs != nbMySegs) ? true:false );
	}

	/* (non-Javadoc)
	 * @see br.ufmg.dees.insane.model.femModel.element.ParametricElement#update()
	 */
	@Override
	public void update() {
		// TODO Auto-generated method stub
		super.update();
	}

}
